/**
 * @Author       : Hejh(3010733382@qq.com)
 * @Version      : V1.0
 * @Date         : 2024-03-21 15:47:50
 * @Description  : 文件分片上传的工具类
 */
const fs = require('fs')
const path = require('path')
let chunkDir = path.join(__dirname, '../public/upload/chunktemp')
let fileInfoDir = path.join(__dirname, '../public/upload/filetemp')
let fileDir = path.join(__dirname, '../public/upload/file')

// 判断路径文件是否存在
async function exists(path) {
  try {
    await fs.promises.stat(path)
    return true
  } catch {
    return false
  }
}

// 创建存放临时文件的目录
function createDir() {
  function existsSync(path) {
    try {
      fs.statSync(path)
      return true
    } catch {
      return false
    }
  }
  function _createDir(path) {
    if (!existsSync(path)) {
      fs.mkdirSync(path)
    }
  }
  _createDir(chunkDir)
  _createDir(fileInfoDir)
  _createDir(fileDir)
}

createDir()

async function generatStoragePath() {
  // const chunkDir = path.join(storage.path, './upload/chunktemp')
  // let fileInfoDir = path.join(storage.path, './upload/filetemp')
  // let fileDir = path.join(storage.path, './upload/file')

  function existsSync(path) {
    try {
      fs.statSync(path)
      return true
    } catch {
      return false
    }
  }
  function _createDir(path) {
    if (!existsSync(path)) {
      fs.mkdirSync(path, { recursive: true })
    }
  }
  _createDir(chunkDir)
  _createDir(fileInfoDir)
  _createDir(fileDir)

  return {
    chunkDir,
    fileInfoDir,
    fileDir
  }
}

module.exports.getUploadPath = generatStoragePath

// 写入需要上传的文件信息
module.exports.writeFileInfo = async (
  fileId,
  filename,
  chunkIds,
  size,
  parentId,
  needs = chunkIds
) => {
  const { fileInfoDir } = await generatStoragePath()
  const absPath = path.join(fileInfoDir, fileId)
  let info = {
    fileId,
    filename,
    size,
    parentId,
    chunkIds, // 全部的分片
    needs // 还需要上传的分片
  }
  await fs.promises.writeFile(absPath, JSON.stringify(info), 'utf-8')
  return info
}

// 根据fileId获取上传的文件信息
module.exports.getFileInfo = async (fileId) => {
  const { fileInfoDir } = await generatStoragePath()
  const absPath = path.join(fileInfoDir, fileId)
  if (!(await exists(absPath))) {
    return null
  }

  const json = await fs.promises.readFile(absPath, 'utf-8')
  return JSON.parse(json)
}

// 处理上传的分片
module.exports.handleChunk = async (fileId, chunkId, chunkBuffer) => {
  let fileInfo = await this.getFileInfo(fileId)
  if (!fileInfo) {
    throw new Error('请先提交文件分片信息')
  }
  if (!fileInfo.chunkIds.includes(chunkId)) {
    throw new Error('该文件没有此分片信息')
  }
  if (!fileInfo.needs.includes(chunkId)) {
    // 此分片已经上传
    return fileInfo.needs
  }
  // 处理分片
  await createFileChunk(chunkId, chunkBuffer)
  // 添加分片信息到文件信息
  fileInfo = await addChunkToFileInfo(chunkId, fileId)
  // 还有需要的分片吗？
  if (fileInfo.needs.length > 0) {
    return fileInfo.needs
  } else {
    // 全部传完了
    await combine(fileInfo)
    return []
  }
}

/**
 * 创建一个文件分片
 * @param {*} id chunkId
 * @param {*} buffer file字节文件buffer
 * @returns
 */
module.exports.createFileChunk = async (id, buffer) => {
  const { chunkDir } = await generatStoragePath()
  const absPath = path.join(chunkDir, id)
  if (!(await exists(absPath))) {
    await fs.promises.writeFile(absPath, buffer) // 写入文件
  }
  return {
    id,
    filename: id,
    path: absPath
  }
}

/**
 * 添加分片信息到文件协商信息里面
 * @param {*} chunkId
 * @param {*} fileId
 * @returns
 */
module.exports.addChunkToFileInfo = async (chunkId, fileId) => {
  const fileInfo = await this.getFileInfo(fileId)
  if (!fileInfo) {
    return null
  }
  // 该分片已经上传，从needs数组中去除
  fileInfo.needs = fileInfo.needs.filter((it) => it !== chunkId)
  return await this.writeFileInfo(
    fileId,
    fileInfo.filename,
    fileInfo.chunkIds,
    fileInfo.size,
    fileInfo.parentId,
    fileInfo.needs
  )
}

// 分片合并
module.exports.combine = async (fileInfo) => {
  const { chunkDir, fileInfoDir, fileDir } = await generatStoragePath()
  //1. 将该文件的所有分片依次合并
  const target = path.join(fileDir, fileInfo.fileId) + fileInfo.filename
  const url = `/upload/file/${fileInfo.fileId}${fileInfo.filename}`

  async function _move(chunkId) {
    const chunkPath = path.join(chunkDir, chunkId)
    const buffer = await fs.promises.readFile(chunkPath)
    await fs.promises.appendFile(target, buffer)
    fs.promises.rm(chunkPath)
  }
  for (const chunkId of fileInfo.chunkIds) {
    await _move(chunkId)
  }

  //2. 删除文件信息
  fs.promises.rm(path.join(fileInfoDir, fileInfo.fileId))

  return url
}
